﻿using System;
using System.Collections.Generic;
using FairyGUI.Utils;
using UnityEngine;

namespace FairyGUI
{
	/// <summary>
	/// 
	/// </summary>
	public delegate void PlayCompleteCallback();

	/// <summary>
	/// 
	/// </summary>
	public delegate void TransitionHook();

	/// <summary>
	/// 
	/// </summary>
	public class Transition : ITweenListener
	{
		/// <summary>
		/// 动效的名称。在编辑器里设定。
		/// </summary>
		public string name { get; private set; }

		/// <summary>
		/// 当你启动了自动合批，动效里有涉及到XY、大小、旋转等的改变，如果你观察到元件的显示深度在播放过程中有错误，可以开启这个选项。
		/// </summary>
		public bool invalidateBatchingEveryFrame;

		GComponent _owner;
		TransitionItem[] _items;
		int _totalTimes;
		int _totalTasks;
		bool _playing;
		bool _paused;
		float _ownerBaseX;
		float _ownerBaseY;
		PlayCompleteCallback _onComplete;
		int _options;
		bool _reversed;
		float _totalDuration;
		bool _autoPlay;
		int _autoPlayTimes;
		float _autoPlayDelay;
		float _timeScale;
		bool _ignoreEngineTimeScale;
		float _startTime;
		float _endTime;
		GTweenCallback _delayedCallDelegate;
		GTweenCallback1 _delayedCallDelegate2;

		const int OPTION_IGNORE_DISPLAY_CONTROLLER = 1;
		const int OPTION_AUTO_STOP_DISABLED = 2;
		const int OPTION_AUTO_STOP_AT_END = 4;

		public Transition(GComponent owner)
		{
			_owner = owner;
			_timeScale = 1;
			_ignoreEngineTimeScale = true;

			_delayedCallDelegate = OnDelayedPlay;
			_delayedCallDelegate2 = OnDelayedPlayItem;
		}

		/// <summary>
		/// 
		/// </summary>
		public void Play()
		{
			_Play(1, 0, 0, -1, null, false);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="onComplete"></param>
		public void Play(PlayCompleteCallback onComplete)
		{
			_Play(1, 0, 0, -1, onComplete, false);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="times"></param>
		/// <param name="delay"></param>
		/// <param name="onComplete"></param>
		public void Play(int times, float delay, PlayCompleteCallback onComplete)
		{
			_Play(times, delay, 0, -1, onComplete, false);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="times"></param>
		/// <param name="delay"></param>
		/// <param name="startTime"></param>
		/// <param name="endTime"></param>
		/// <param name="onComplete"></param>
		public void Play(int times, float delay, float startTime, float endTime, PlayCompleteCallback onComplete)
		{
			_Play(times, delay, startTime, endTime, onComplete, false);
		}

		/// <summary>
		/// 
		/// </summary>
		public void PlayReverse()
		{
			_Play(1, 0, 0, -1, null, true);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="onComplete"></param>
		public void PlayReverse(PlayCompleteCallback onComplete)
		{
			_Play(1, 0, 0, -1, onComplete, true);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="times"></param>
		/// <param name="delay"></param>
		/// <param name="onComplete"></param>
		public void PlayReverse(int times, float delay, PlayCompleteCallback onComplete)
		{
			_Play(times, delay, 0, -1, onComplete, true);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="value"></param>
		public void ChangePlayTimes(int value)
		{
			_totalTimes = value;
		}

		/// <summary>
		/// 设置动效是否自动播放。
		/// </summary>
		/// <param name="autoPlay"></param>
		/// <param name="times"></param>
		/// <param name="delay"></param>
		public void SetAutoPlay(bool autoPlay, int times, float delay)
		{
			if (_autoPlay != autoPlay)
			{
				_autoPlay = autoPlay;
				_autoPlayTimes = times;
				_autoPlayDelay = delay;
				if (_autoPlay)
				{
					if (_owner.onStage)
						Play(times, delay, null);
				}
				else
				{
					if (!_owner.onStage)
						Stop(false, true);
				}
			}
		}

		void _Play(int times, float delay, float startTime, float endTime, PlayCompleteCallback onComplete, bool reverse)
		{
			Stop(true, true);

			_totalTimes = times;
			_reversed = reverse;
			_startTime = startTime;
			_endTime = endTime;
			_playing = true;
			_paused = false;
			_onComplete = onComplete;

			int cnt = _items.Length;
			for (int i = 0; i < cnt; i++)
			{
				TransitionItem item = _items[i];
				if (item.target == null)
				{
					if (item.targetId.Length > 0)
						item.target = _owner.GetChildById(item.targetId);
					else
						item.target = _owner;
				}
				else if (item.target != _owner && item.target.parent != _owner) //maybe removed
					item.target = null;

				if (item.target != null && item.type == TransitionActionType.Transition)
				{
					TValue_Transition value = (TValue_Transition)item.value;
					Transition trans = ((GComponent)item.target).GetTransition(value.transName);
					if (trans == this)
						trans = null;
					if (trans != null)
					{
						if (value.playTimes == 0) //stop
						{
							int j;
							for (j = i - 1; j >= 0; j--)
							{
								TransitionItem item2 = _items[j];
								if (item2.type == TransitionActionType.Transition)
								{
									TValue_Transition value2 = (TValue_Transition)item2.value;
									if (value2.trans == trans)
									{
										value2.stopTime = item.time - item2.time;
										break;
									}
								}
							}
							if (j < 0)
								value.stopTime = 0;
							else
								trans = null; //no need to handle stop anymore
						}
						else
							value.stopTime = -1;
					}
					value.trans = trans;
				}
			}

			if (delay == 0)
				OnDelayedPlay();
			else
				GTween.DelayedCall(delay).SetTarget(this).OnComplete(_delayedCallDelegate);
		}

		/// <summary>
		/// 
		/// </summary>
		public void Stop()
		{
			Stop(true, false);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="setToComplete"></param>
		/// <param name="processCallback"></param>
		public void Stop(bool setToComplete, bool processCallback)
		{
			if (!_playing)
				return;

			_playing = false;
			_totalTasks = 0;
			_totalTimes = 0;
			PlayCompleteCallback func = _onComplete;
			_onComplete = null;

			GTween.Kill(this);//delay start

			int cnt = _items.Length;
			if (_reversed)
			{
				for (int i = cnt - 1; i >= 0; i--)
				{
					TransitionItem item = _items[i];
					if (item.target == null)
						continue;

					StopItem(item, setToComplete);
				}
			}
			else
			{
				for (int i = 0; i < cnt; i++)
				{
					TransitionItem item = _items[i];
					if (item.target == null)
						continue;

					StopItem(item, setToComplete);
				}
			}

			if (processCallback && func != null)
				func();

		}

		void StopItem(TransitionItem item, bool setToComplete)
		{
			if (item.displayLockToken != 0)
			{
				item.target.ReleaseDisplayLock(item.displayLockToken);
				item.displayLockToken = 0;
			}

			if (item.tweener != null)
			{
				item.tweener.Kill(setToComplete);
				item.tweener = null;

				if (item.type == TransitionActionType.Shake && !setToComplete) //震动必须归位，否则下次就越震越远了。
				{
					item.target._gearLocked = true;
					item.target.SetXY(item.target.x - ((TValue_Shake)item.value).lastOffset.x, item.target.y - ((TValue_Shake)item.value).lastOffset.y);
					item.target._gearLocked = false;

					_owner.InvalidateBatchingState(true);
				}
			}

			if (item.type == TransitionActionType.Transition)
			{
				TValue_Transition value = (TValue_Transition)item.value;
				if (value.trans != null)
					value.trans.Stop(setToComplete, false);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="paused"></param>
		public void SetPaused(bool paused)
		{
			if (!_playing || _paused == paused)
				return;

			_paused = paused;
			GTweener tweener = GTween.GetTween(this);
			if (tweener != null)
				tweener.SetPaused(paused);

			int cnt = _items.Length;
			for (int i = 0; i < cnt; i++)
			{
				TransitionItem item = _items[i];
				if (item.target == null)
					continue;

				if (item.type == TransitionActionType.Transition)
				{
					if (((TValue_Transition)item.value).trans != null)
						((TValue_Transition)item.value).trans.SetPaused(paused);
				}
				else if (item.type == TransitionActionType.Animation)
				{
					if (paused)
					{
						((TValue_Animation)item.value).flag = ((IAnimationGear)item.target).playing;
						((IAnimationGear)item.target).playing = false;
					}
					else
						((IAnimationGear)item.target).playing = ((TValue_Animation)item.value).flag;
				}

				if (item.tweener != null)
					item.tweener.SetPaused(paused);
			}
		}

		public void Dispose()
		{
			if (_playing)
				GTween.Kill(this);//delay start

			int cnt = _items.Length;
			for (int i = 0; i < cnt; i++)
			{
				TransitionItem item = _items[i];
				if (item.tweener != null)
				{
					item.tweener.Kill();
					item.tweener = null;
				}

				item.target = null;
				item.hook = null;
				if (item.tweenConfig != null)
					item.tweenConfig.endHook = null;
			}

			_playing = false;
			_onComplete = null;
		}

		/// <summary>
		/// 
		/// </summary>
		public bool playing
		{
			get { return _playing; }
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="label"></param>
		/// <param name="aParams"></param>
		public void SetValue(string label, params object[] aParams)
		{
			int cnt = _items.Length;
			object value;
			bool found = false;
			for (int i = 0; i < cnt; i++)
			{
				TransitionItem item = _items[i];
				if (item.label == label)
				{
					if (item.tweenConfig != null)
						value = item.tweenConfig.startValue;
					else
						value = item.value;
					found = true;
				}
				else if (item.tweenConfig != null && item.tweenConfig.endLabel == label)
				{
					value = item.tweenConfig.endValue;
					found = true;
				}
				else
					continue;

				switch (item.type)
				{
					case TransitionActionType.XY:
					case TransitionActionType.Size:
					case TransitionActionType.Pivot:
					case TransitionActionType.Scale:
					case TransitionActionType.Skew:
						{
							TValue tvalue = (TValue)value;
							tvalue.b1 = true;
							tvalue.b2 = true;
							tvalue.f1 = Convert.ToSingle(aParams[0]);
							tvalue.f2 = Convert.ToSingle(aParams[1]);
						}
						break;

					case TransitionActionType.Alpha:
						((TValue)value).f1 = Convert.ToSingle(aParams[0]);
						break;

					case TransitionActionType.Rotation:
						((TValue)value).f1 = Convert.ToSingle(aParams[0]);
						break;

					case TransitionActionType.Color:
						((TValue)value).color = (Color)aParams[0];
						break;

					case TransitionActionType.Animation:
						{
							TValue_Animation tvalue = (TValue_Animation)value;
							tvalue.frame = Convert.ToInt32(aParams[0]);
							if (aParams.Length > 1)
								tvalue.playing = Convert.ToBoolean(aParams[1]);
						}
						break;

					case TransitionActionType.Visible:
						((TValue_Visible)value).visible = Convert.ToBoolean(aParams[0]);
						break;

					case TransitionActionType.Sound:
						{
							TValue_Sound tvalue = (TValue_Sound)value;
							tvalue.sound = (string)aParams[0];
							if (aParams.Length > 1)
								tvalue.volume = Convert.ToSingle(aParams[1]);
						}
						break;

					case TransitionActionType.Transition:
						{
							TValue_Transition tvalue = (TValue_Transition)value;
							tvalue.transName = (string)aParams[0];
							if (aParams.Length > 1)
								tvalue.playTimes = Convert.ToInt32(aParams[1]);
						}
						break;

					case TransitionActionType.Shake:
						{
							((TValue_Shake)value).amplitude = Convert.ToSingle(aParams[0]);
							if (aParams.Length > 1)
								((TValue_Shake)value).duration = Convert.ToSingle(aParams[1]);
						}
						break;

					case TransitionActionType.ColorFilter:
						{
							TValue tvalue = (TValue)value;
							tvalue.f1 = Convert.ToSingle(aParams[0]);
							tvalue.f2 = Convert.ToSingle(aParams[1]);
							tvalue.f3 = Convert.ToSingle(aParams[2]);
							tvalue.f4 = Convert.ToSingle(aParams[3]);
						}
						break;

					case TransitionActionType.Text:
					case TransitionActionType.Icon:
						((TValue_Text)value).text = (string)aParams[0];
						break;
				}
			}

			if (!found)
				throw new Exception("label not exists");
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="label"></param>
		/// <param name="callback"></param>
		public void SetHook(string label, TransitionHook callback)
		{
			int cnt = _items.Length;
			bool found = false;
			for (int i = 0; i < cnt; i++)
			{
				TransitionItem item = _items[i];
				if (item.label == label)
				{
					item.hook = callback;
					found = true;
					break;
				}
				else if (item.tweenConfig != null && item.tweenConfig.endLabel == label)
				{
					item.tweenConfig.endHook = callback;
					found = true;
					break;
				}
			}
			if (!found)
				throw new Exception("label not exists");
		}

		/// <summary>
		/// 
		/// </summary>
		public void ClearHooks()
		{
			int cnt = _items.Length;
			for (int i = 0; i < cnt; i++)
			{
				TransitionItem item = _items[i];
				item.hook = null;
				if (item.tweenConfig != null)
					item.tweenConfig.endHook = null;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="label"></param>
		/// <param name="newTarget"></param>
		public void SetTarget(string label, GObject newTarget)
		{
			int cnt = _items.Length;
			bool found = false;
			for (int i = 0; i < cnt; i++)
			{
				TransitionItem item = _items[i];
				if (item.label == label)
				{
					item.targetId = (newTarget == _owner || newTarget == null) ? string.Empty : newTarget.id;
					if (_playing)
					{
						if (item.targetId.Length > 0)
							item.target = _owner.GetChildById(item.targetId);
						else
							item.target = _owner;
					}
					else
						item.target = null;
					found = true;
				}
			}
			if (!found)
				throw new Exception("label not exists");
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="label"></param>
		/// <param name="value"></param>
		public void SetDuration(string label, float value)
		{
			int cnt = _items.Length;
			bool found = false;
			for (int i = 0; i < cnt; i++)
			{
				TransitionItem item = _items[i];
				if (item.tweenConfig != null && item.label == label)
				{
					item.tweenConfig.duration = value;
					found = true;
				}
			}

			if (!found)
				throw new Exception("label not exists or not a tween label");
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="label"></param>
		/// <returns></returns>
		public float GetLabelTime(string label)
		{
			int cnt = _items.Length;
			for (int i = 0; i < cnt; i++)
			{
				TransitionItem item = _items[i];
				if (item.label == label)
					return item.time;
				else if (item.tweenConfig != null && item.tweenConfig.endLabel == label)
					return item.time + item.tweenConfig.duration;
			}

			return float.NaN;
		}

		/// <summary>
		/// 
		/// </summary>
		public float timeScale
		{
			get { return _timeScale; }
			set
			{
				if (_timeScale != value)
				{
					_timeScale = value;

					int cnt = _items.Length;
					for (int i = 0; i < cnt; i++)
					{
						TransitionItem item = _items[i];
						if (item.tweener != null)
							item.tweener.SetTimeScale(value);
						else if (item.type == TransitionActionType.Transition)
						{
							if (((TValue_Transition)item.value).trans != null)
								((TValue_Transition)item.value).trans.timeScale = value;
						}
						else if (item.type == TransitionActionType.Animation)
						{
							if (item.target != null)
								((IAnimationGear)item.target).timeScale = value;
						}
					}
				}
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public bool ignoreEngineTimeScale
		{
			get { return _ignoreEngineTimeScale; }
			set
			{
				if (_ignoreEngineTimeScale != value)
				{
					_ignoreEngineTimeScale = value;

					int cnt = _items.Length;
					for (int i = 0; i < cnt; i++)
					{
						TransitionItem item = _items[i];
						if (item.tweener != null)
							item.tweener.SetIgnoreEngineTimeScale(value);
						else if (item.type == TransitionActionType.Transition)
						{
							if (((TValue_Transition)item.value).trans != null)
								((TValue_Transition)item.value).trans.ignoreEngineTimeScale = value;
						}
						else if (item.type == TransitionActionType.Animation)
						{
							if (item.target != null)
								((IAnimationGear)item.target).ignoreEngineTimeScale = value;
						}
					}
				}
			}
		}

		internal void UpdateFromRelations(string targetId, float dx, float dy)
		{
			int cnt = _items.Length;
			if (cnt == 0)
				return;

			for (int i = 0; i < cnt; i++)
			{
				TransitionItem item = _items[i];
				if (item.type == TransitionActionType.XY && item.targetId == targetId)
				{
					if (item.tweenConfig != null)
					{
						item.tweenConfig.startValue.f1 += dx;
						item.tweenConfig.startValue.f2 += dy;
						item.tweenConfig.endValue.f1 += dx;
						item.tweenConfig.endValue.f2 += dy;
					}
					else
					{
						((TValue)item.value).f1 += dx;
						((TValue)item.value).f2 += dy;
					}
				}
			}
		}

		internal void OnOwnerAddedToStage()
		{
			if (_autoPlay && !_playing)
				Play(_autoPlayTimes, _autoPlayDelay, null);
		}

		internal void OnOwnerRemovedFromStage()
		{
			if ((_options & OPTION_AUTO_STOP_DISABLED) == 0)
				Stop((_options & OPTION_AUTO_STOP_AT_END) != 0 ? true : false, false);
		}

		void OnDelayedPlay()
		{
			InternalPlay();

			_playing = _totalTasks > 0;
			if (_playing)
			{
				if ((_options & OPTION_IGNORE_DISPLAY_CONTROLLER) != 0)
				{
					int cnt = _items.Length;
					for (int i = 0; i < cnt; i++)
					{
						TransitionItem item = _items[i];
						if (item.target != null && item.target != _owner)
							item.displayLockToken = item.target.AddDisplayLock();
					}
				}
			}
			else if (_onComplete != null)
			{
				PlayCompleteCallback func = _onComplete;
				_onComplete = null;
				func();
			}
		}

		void InternalPlay()
		{
			_ownerBaseX = _owner.x;
			_ownerBaseY = _owner.y;

			_totalTasks = 0;

			bool needSkipAnimations = false;
			int cnt = _items.Length;
			if (!_reversed)
			{
				for (int i = 0; i < cnt; i++)
				{
					TransitionItem item = _items[i];
					if (item.target == null)
						continue;

					if (item.type == TransitionActionType.Animation && _startTime != 0 && item.time <= _startTime)
					{
						needSkipAnimations = true;
						((TValue_Animation)item.value).flag = false;
					}
					else
						PlayItem(item);
				}
			}
			else
			{
				for (int i = cnt - 1; i >= 0; i--)
				{
					TransitionItem item = _items[i];
					if (item.target == null)
						continue;

					PlayItem(item);
				}
			}

			if (needSkipAnimations)
				SkipAnimations();
		}

		void PlayItem(TransitionItem item)
		{
			float time;
			if (item.tweenConfig != null)
			{
				if (_reversed)
					time = (_totalDuration - item.time - item.tweenConfig.duration);
				else
					time = item.time;

				if (_endTime == -1 || time <= _endTime)
				{
					TValue startValue;
					TValue endValue;

					if (_reversed)
					{
						startValue = item.tweenConfig.endValue;
						endValue = item.tweenConfig.startValue;
					}
					else
					{
						startValue = item.tweenConfig.startValue;
						endValue = item.tweenConfig.endValue;
					}

					((TValue)item.value).b1 = startValue.b1 || endValue.b1;
					((TValue)item.value).b2 = startValue.b2 || endValue.b2;

					switch (item.type)
					{
						case TransitionActionType.XY:
						case TransitionActionType.Size:
						case TransitionActionType.Scale:
						case TransitionActionType.Skew:
							item.tweener = GTween.To(startValue.vec2, endValue.vec2, item.tweenConfig.duration);
							break;

						case TransitionActionType.Alpha:
						case TransitionActionType.Rotation:
							item.tweener = GTween.To(startValue.f1, endValue.f1, item.tweenConfig.duration);
							break;

						case TransitionActionType.Color:
							item.tweener = GTween.To(startValue.color, endValue.color, item.tweenConfig.duration);
							break;

						case TransitionActionType.ColorFilter:
							item.tweener = GTween.To(startValue.vec4, endValue.vec4, item.tweenConfig.duration);
							break;
					}

					item.tweener.SetDelay(time)
						.SetEase(item.tweenConfig.easeType)
						.SetRepeat(item.tweenConfig.repeat, item.tweenConfig.yoyo)
						.SetTimeScale(_timeScale)
						.SetIgnoreEngineTimeScale(_ignoreEngineTimeScale)
						.SetTarget(item)
						.SetListener(this);

					if (_endTime >= 0)
						item.tweener.SetBreakpoint(_endTime - time);

					_totalTasks++;
				}
			}
			else if (item.type == TransitionActionType.Shake)
			{
				TValue_Shake value = (TValue_Shake)item.value;

				if (_reversed)
					time = (_totalDuration - item.time - value.duration);
				else
					time = item.time;

				if (_endTime == -1 || time <= _endTime)
				{
					value.lastOffset.Set(0, 0);
					value.offset.Set(0, 0);
					item.tweener = GTween.Shake(Vector3.zero, value.amplitude, value.duration)
						.SetDelay(time)
						.SetTimeScale(_timeScale)
						.SetIgnoreEngineTimeScale(_ignoreEngineTimeScale)
						.SetTarget(item)
						.SetListener(this);

					if (_endTime >= 0)
						item.tweener.SetBreakpoint(_endTime - item.time);

					_totalTasks++;
				}
			}
			else
			{
				if (_reversed)
					time = (_totalDuration - item.time);
				else
					time = item.time;

				if (time <= _startTime)
				{
					ApplyValue(item);
					CallHook(item, false);
				}
				else if (_endTime == -1 || time <= _endTime)
				{
					_totalTasks++;
					item.tweener = GTween.DelayedCall(time)
						.SetTimeScale(_timeScale)
						.SetIgnoreEngineTimeScale(_ignoreEngineTimeScale)
						.SetTarget(item)
						.OnComplete(_delayedCallDelegate2);
				}
			}

			if (item.tweener != null)
				item.tweener.Seek(_startTime);
		}

		void SkipAnimations()
		{
			int frame;
			float playStartTime;
			float playTotalTime;
			TValue_Animation value;
			IAnimationGear target;
			TransitionItem item;

			int cnt = _items.Length;
			for (int i = 0; i < cnt; i++)
			{
				item = _items[i];
				if (item.type != TransitionActionType.Animation || item.time > _startTime)
					continue;

				value = (TValue_Animation)item.value;
				if (value.flag)
					continue;

				target = (IAnimationGear)item.target;
				frame = target.frame;
				playStartTime = target.playing ? 0 : -1;
				playTotalTime = 0;

				for (int j = i; j < cnt; j++)
				{
					item = _items[j];
					if (item.type != TransitionActionType.Animation || item.target != target || item.time > _startTime)
						continue;

					value = (TValue_Animation)item.value;
					value.flag = true;

					if (value.frame != -1)
					{
						frame = value.frame;
						if (value.playing)
							playStartTime = item.time;
						else
							playStartTime = -1;
						playTotalTime = 0;
					}
					else
					{
						if (value.playing)
						{
							if (playStartTime < 0)
								playStartTime = item.time;
						}
						else
						{
							if (playStartTime >= 0)
								playTotalTime += (item.time - playStartTime);
							playStartTime = -1;
						}
					}

					CallHook(item, false);
				}

				if (playStartTime >= 0)
					playTotalTime += (_startTime - playStartTime);

				target.playing = playStartTime >= 0;
				target.frame = frame;
				if (playTotalTime > 0)
					target.Advance(playTotalTime);
			}
		}

		void OnDelayedPlayItem(GTweener tweener)
		{
			TransitionItem item = (TransitionItem)tweener.target;
			item.tweener = null;
			_totalTasks--;

			ApplyValue(item);
			CallHook(item, false);

			CheckAllComplete();
		}

		public void OnTweenStart(GTweener tweener)
		{
			TransitionItem item = (TransitionItem)tweener.target;

			if (item.type == TransitionActionType.XY || item.type == TransitionActionType.Size) //位置和大小要到start才最终确认起始值
			{
				TValue startValue;
				TValue endValue;

				if (_reversed)
				{
					startValue = item.tweenConfig.endValue;
					endValue = item.tweenConfig.startValue;
				}
				else
				{
					startValue = item.tweenConfig.startValue;
					endValue = item.tweenConfig.endValue;
				}

				if (item.type == TransitionActionType.XY)
				{
					if (item.target != _owner)
					{
						if (!startValue.b1)
							startValue.f1 = item.target.x;
						if (!startValue.b2)
							startValue.f2 = item.target.y;
					}
					else
					{
						if (!startValue.b1)
							startValue.f1 = item.target.x - _ownerBaseX;
						if (!startValue.b2)
							startValue.f2 = item.target.y - _ownerBaseY;
					}
				}
				else
				{
					if (!startValue.b1)
						startValue.f1 = item.target.width;
					if (!startValue.b2)
						startValue.f2 = item.target.height;
				}

				if (!endValue.b1)
					endValue.f1 = startValue.f1;
				if (!endValue.b2)
					endValue.f2 = startValue.f2;

				tweener.startValue.vec2 = startValue.vec2;
				tweener.endValue.vec2 = endValue.vec2;
			}

			CallHook(item, false);
		}

		public void OnTweenUpdate(GTweener tweener)
		{
			TransitionItem item = (TransitionItem)tweener.target;
			switch (item.type)
			{
				case TransitionActionType.XY:
				case TransitionActionType.Size:
				case TransitionActionType.Scale:
				case TransitionActionType.Skew:
					((TValue)item.value).vec2 = tweener.value.vec2;
					break;

				case TransitionActionType.Alpha:
				case TransitionActionType.Rotation:
					((TValue)item.value).f1 = tweener.value.x;
					break;

				case TransitionActionType.Color:
					((TValue)item.value).color = tweener.value.color;
					break;

				case TransitionActionType.ColorFilter:
					((TValue)item.value).vec4 = tweener.value.vec4;
					break;

				case TransitionActionType.Shake:
					((TValue_Shake)item.value).offset = tweener.deltaValue.vec2;
					break;
			}
			ApplyValue(item);
		}

		public void OnTweenComplete(GTweener tweener)
		{
			TransitionItem item = (TransitionItem)tweener.target;
			item.tweener = null;
			_totalTasks--;

			if (tweener.allCompleted) //当整体播放结束时间在这个tween的中间时不应该调用结尾钩子
				CallHook(item, true);

			CheckAllComplete();
		}

		void OnPlayTransCompleted(TransitionItem item)
		{
			_totalTasks--;

			CheckAllComplete();
		}

		void CallHook(TransitionItem item, bool tweenEnd)
		{
			if (tweenEnd)
			{
				if (item.tweenConfig != null && item.tweenConfig.endHook != null)
					item.tweenConfig.endHook();
			}
			else
			{
				if (item.time >= _startTime && item.hook != null)
					item.hook();
			}
		}

		void CheckAllComplete()
		{
			if (_playing && _totalTasks == 0)
			{
				if (_totalTimes < 0)
				{
					InternalPlay();
				}
				else
				{
					_totalTimes--;
					if (_totalTimes > 0)
						InternalPlay();
					else
					{
						_playing = false;

						int cnt = _items.Length;
						for (int i = 0; i < cnt; i++)
						{
							TransitionItem item = _items[i];
							if (item.target != null && item.displayLockToken != 0)
							{
								item.target.ReleaseDisplayLock(item.displayLockToken);
								item.displayLockToken = 0;
							}
						}

						if (_onComplete != null)
						{
							PlayCompleteCallback func = _onComplete;
							_onComplete = null;
							func();
						}
					}
				}
			}
		}

		void ApplyValue(TransitionItem item)
		{
			item.target._gearLocked = true;

			switch (item.type)
			{
				case TransitionActionType.XY:
					{
						TValue value = (TValue)item.value;
						if (item.target == _owner)
						{
							float f1, f2;
							if (!value.b1)
								f1 = item.target.x;
							else
								f1 = value.f1 + _ownerBaseX;
							if (!value.b2)
								f2 = item.target.y;
							else
								f2 = value.f2 + _ownerBaseY;
							item.target.SetXY(f1, f2);
						}
						else
						{
							if (!value.b1)
								value.f1 = item.target.x;
							if (!value.b2)
								value.f2 = item.target.y;
							item.target.SetXY(value.f1, value.f2);
						}
						if (invalidateBatchingEveryFrame)
							_owner.InvalidateBatchingState(true);
					}
					break;

				case TransitionActionType.Size:
					{
						TValue value = (TValue)item.value;
						if (!value.b1)
							value.f1 = item.target.width;
						if (!value.b2)
							value.f2 = item.target.height;
						item.target.SetSize(value.f1, value.f2);
						if (invalidateBatchingEveryFrame)
							_owner.InvalidateBatchingState(true);
					}
					break;

				case TransitionActionType.Pivot:
					item.target.SetPivot(((TValue)item.value).f1, ((TValue)item.value).f2, item.target.pivotAsAnchor);
					if (invalidateBatchingEveryFrame)
						_owner.InvalidateBatchingState(true);
					break;

				case TransitionActionType.Alpha:
					item.target.alpha = ((TValue)item.value).f1;
					break;

				case TransitionActionType.Rotation:
					item.target.rotation = ((TValue)item.value).f1;
					if (invalidateBatchingEveryFrame)
						_owner.InvalidateBatchingState(true);
					break;

				case TransitionActionType.Scale:
					item.target.SetScale(((TValue)item.value).f1, ((TValue)item.value).f2);
					if (invalidateBatchingEveryFrame)
						_owner.InvalidateBatchingState(true);
					break;

				case TransitionActionType.Skew:
					item.target.skew = ((TValue)item.value).vec2;
					if (invalidateBatchingEveryFrame)
						_owner.InvalidateBatchingState(true);
					break;

				case TransitionActionType.Color:
					((IColorGear)item.target).color = ((TValue)item.value).color;
					break;

				case TransitionActionType.Animation:
					{
						TValue_Animation value = (TValue_Animation)item.value;
						if (value.frame >= 0)
							((IAnimationGear)item.target).frame = value.frame;
						((IAnimationGear)item.target).playing = value.playing;
						((IAnimationGear)item.target).timeScale = _timeScale;
						((IAnimationGear)item.target).ignoreEngineTimeScale = _ignoreEngineTimeScale;
					}
					break;

				case TransitionActionType.Visible:
					item.target.visible = ((TValue_Visible)item.value).visible;
					break;

				case TransitionActionType.Shake:
					{
						TValue_Shake value = (TValue_Shake)item.value;
						item.target.SetXY(item.target.x - value.lastOffset.x + value.offset.x, item.target.y - value.lastOffset.y + value.offset.y);
						value.lastOffset = value.offset;

						if (invalidateBatchingEveryFrame)
							_owner.InvalidateBatchingState(true);
					}
					break;

				case TransitionActionType.Transition:
					if (_playing)
					{
						TValue_Transition value = (TValue_Transition)item.value;
						if (value.trans != null)
						{
							_totalTasks++;

							float startTime = _startTime > item.time ? (_startTime - item.time) : 0;
							float endTime = _endTime >= 0 ? (_endTime - item.time) : -1;
							if (value.stopTime >= 0 && (endTime < 0 || endTime > value.stopTime))
								endTime = value.stopTime;
							value.trans.timeScale = _timeScale;
							value.trans.ignoreEngineTimeScale = _ignoreEngineTimeScale;
							value.trans._Play(value.playTimes, 0, startTime, endTime, value.playCompleteDelegate, _reversed);
						}
					}
					break;

				case TransitionActionType.Sound:
					if (_playing && item.time >= _startTime)
					{
						TValue_Sound value = (TValue_Sound)item.value;
						if (value.audioClip == null)
						{
							if (UIConfig.soundLoader == null || value.sound.StartsWith(UIPackage.URL_PREFIX))
								value.audioClip = UIPackage.GetItemAssetByURL(value.sound) as NAudioClip;
							else
								value.audioClip = UIConfig.soundLoader(value.sound);
						}

						if (value.audioClip != null && value.audioClip.nativeClip != null)
							Stage.inst.PlayOneShotSound(value.audioClip.nativeClip, value.volume);
					}
					break;

				case TransitionActionType.ColorFilter:
					{
						TValue value = (TValue)item.value;
						ColorFilter cf = item.target.filter as ColorFilter;
						if (cf == null)
						{
							cf = new ColorFilter();
							item.target.filter = cf;
						}
						else
							cf.Reset();

						cf.AdjustBrightness(value.f1);
						cf.AdjustContrast(value.f2);
						cf.AdjustSaturation(value.f3);
						cf.AdjustHue(value.f4);
					}
					break;

				case TransitionActionType.Text:
					item.target.text = ((TValue_Text)item.value).text;
					break;

				case TransitionActionType.Icon:
					item.target.icon = ((TValue_Text)item.value).text;
					break;
			}

			item.target._gearLocked = false;
		}

		public void Setup(ByteBuffer buffer)
		{
			this.name = buffer.ReadS();
			_options = buffer.ReadInt();
			_autoPlay = buffer.ReadBool();
			_autoPlayTimes = buffer.ReadInt();
			_autoPlayDelay = buffer.ReadFloat();

			int cnt = buffer.ReadShort();
			_items = new TransitionItem[cnt];
			for (int i = 0; i < cnt; i++)
			{
				int dataLen = buffer.ReadShort();
				int curPos = buffer.position;

				buffer.Seek(curPos, 0);

				TransitionItem item = new TransitionItem((TransitionActionType)buffer.ReadByte());
				_items[i] = item;

				item.time = buffer.ReadFloat();
				int targetId = buffer.ReadShort();
				if (targetId < 0)
					item.targetId = string.Empty;
				else
					item.targetId = _owner.GetChildAt(targetId).id;
				item.label = buffer.ReadS();

				if (buffer.ReadBool())
				{
					buffer.Seek(curPos, 1);

					item.tweenConfig = new TweenConfig();
					item.tweenConfig.duration = buffer.ReadFloat();
					if (item.time + item.tweenConfig.duration > _totalDuration)
						_totalDuration = item.time + item.tweenConfig.duration;
					item.tweenConfig.easeType = (EaseType)buffer.ReadByte();
					item.tweenConfig.repeat = buffer.ReadInt();
					item.tweenConfig.yoyo = buffer.ReadBool();
					item.tweenConfig.endLabel = buffer.ReadS();

					buffer.Seek(curPos, 2);

					DecodeValue(item, buffer, item.tweenConfig.startValue);

					buffer.Seek(curPos, 3);

					DecodeValue(item, buffer, item.tweenConfig.endValue);
				}
				else
				{
					if (item.time > _totalDuration)
						_totalDuration = item.time;

					buffer.Seek(curPos, 2);

					DecodeValue(item, buffer, item.value);
				}

				buffer.position = curPos + dataLen;
			}
		}

		void DecodeValue(TransitionItem item, ByteBuffer buffer, object value)
		{
			switch (item.type)
			{
				case TransitionActionType.XY:
				case TransitionActionType.Size:
				case TransitionActionType.Pivot:
				case TransitionActionType.Skew:
					{
						TValue tvalue = (TValue)value;
						tvalue.b1 = buffer.ReadBool();
						tvalue.b2 = buffer.ReadBool();
						tvalue.f1 = buffer.ReadFloat();
						tvalue.f2 = buffer.ReadFloat();
					}
					break;

				case TransitionActionType.Alpha:
				case TransitionActionType.Rotation:
					((TValue)value).f1 = buffer.ReadFloat();
					break;

				case TransitionActionType.Scale:
					((TValue)value).f1 = buffer.ReadFloat();
					((TValue)value).f2 = buffer.ReadFloat();
					break;

				case TransitionActionType.Color:
					((TValue)value).color = buffer.ReadColor();
					break;

				case TransitionActionType.Animation:
					((TValue_Animation)value).playing = buffer.ReadBool();
					((TValue_Animation)value).frame = buffer.ReadInt();
					break;

				case TransitionActionType.Visible:
					((TValue_Visible)value).visible = buffer.ReadBool();
					break;

				case TransitionActionType.Sound:
					((TValue_Sound)value).sound = buffer.ReadS();
					((TValue_Sound)value).volume = buffer.ReadFloat();
					break;

				case TransitionActionType.Transition:
					((TValue_Transition)value).transName = buffer.ReadS();
					((TValue_Transition)value).playTimes = buffer.ReadInt();
					((TValue_Transition)value).playCompleteDelegate = () => { OnPlayTransCompleted(item); };
					break;

				case TransitionActionType.Shake:
					((TValue_Shake)value).amplitude = buffer.ReadFloat();
					((TValue_Shake)value).duration = buffer.ReadFloat();
					break;

				case TransitionActionType.ColorFilter:
					{
						TValue tvalue = (TValue)value;
						tvalue.f1 = buffer.ReadFloat();
						tvalue.f2 = buffer.ReadFloat();
						tvalue.f3 = buffer.ReadFloat();
						tvalue.f4 = buffer.ReadFloat();
					}
					break;

				case TransitionActionType.Text:
				case TransitionActionType.Icon:
					((TValue_Text)value).text = buffer.ReadS();
					break;
			}
		}
	}

	class TransitionItem
	{
		public float time;
		public string targetId;
		public TransitionActionType type;
		public TweenConfig tweenConfig;
		public string label;
		public object value;
		public TransitionHook hook;

		//running properties
		public GTweener tweener;
		public GObject target;
		public uint displayLockToken;

		public TransitionItem(TransitionActionType type)
		{
			this.type = type;

			switch (type)
			{
				case TransitionActionType.XY:
				case TransitionActionType.Size:
				case TransitionActionType.Scale:
				case TransitionActionType.Pivot:
				case TransitionActionType.Skew:
				case TransitionActionType.Alpha:
				case TransitionActionType.Rotation:
				case TransitionActionType.Color:
				case TransitionActionType.ColorFilter:
					value = new TValue();
					break;

				case TransitionActionType.Animation:
					value = new TValue_Animation();
					break;

				case TransitionActionType.Shake:
					value = new TValue_Shake();
					break;

				case TransitionActionType.Sound:
					value = new TValue_Sound();
					break;

				case TransitionActionType.Transition:
					value = new TValue_Transition();
					break;

				case TransitionActionType.Visible:
					value = new TValue_Visible();
					break;

				case TransitionActionType.Text:
				case TransitionActionType.Icon:
					value = new TValue_Text();
					break;
			}
		}
	}

	class TweenConfig
	{
		public float duration;
		public EaseType easeType;
		public int repeat;
		public bool yoyo;

		public TValue startValue;
		public TValue endValue;

		public string endLabel;
		public TransitionHook endHook;

		public TweenConfig()
		{
			easeType = EaseType.QuadOut;
			startValue = new TValue();
			endValue = new TValue();
		}
	}

	class TValue_Visible
	{
		public bool visible;
	}

	class TValue_Animation
	{
		public int frame;
		public bool playing;
		public bool flag;
	}

	class TValue_Sound
	{
		public string sound;
		public float volume;
		public NAudioClip audioClip;
	}

	class TValue_Transition
	{
		public string transName;
		public int playTimes;
		public Transition trans;
		public PlayCompleteCallback playCompleteDelegate;
		public float stopTime;
	}

	class TValue_Shake
	{
		public float amplitude;
		public float duration;
		public Vector2 lastOffset;
		public Vector2 offset;
	}

	class TValue_Text
	{
		public string text;
	}

	class TValue
	{
		public float f1;
		public float f2;
		public float f3;
		public float f4;
		public bool b1;
		public bool b2;

		public TValue()
		{
			b1 = true;
			b2 = true;
		}

		public void Copy(TValue source)
		{
			this.f1 = source.f1;
			this.f2 = source.f2;
			this.f3 = source.f3;
			this.f4 = source.f4;
			this.b1 = source.b1;
			this.b2 = source.b2;
		}

		public Vector2 vec2
		{
			get { return new Vector2(f1, f2); }
			set
			{
				f1 = value.x;
				f2 = value.y;
			}
		}

		public Vector4 vec4
		{
			get { return new Vector4(f1, f2, f3, f4); }
			set
			{
				f1 = value.x;
				f2 = value.y;
				f3 = value.z;
				f4 = value.w;
			}
		}

		public Color color
		{
			get { return new Color(f1, f2, f3, f4); }
			set
			{
				f1 = value.r;
				f2 = value.g;
				f3 = value.b;
				f4 = value.a;
			}
		}
	}
}
